/*   Copyright © 2009 Mark Gil Torres
 *
 *   This file is part of mtoolkit.
 *
 *   mtoolkit is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   any later version.
 *
 *   mtoolkit is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with mtoolkit.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef Q_HPP
#define Q_HPP

#include <iostream>

namespace mt {

  template <typename T> class Q;
  template <typename T> std::ostream& operator<<(std::ostream& ostr, Q<T> const& inValue);
  template <typename T> Q<T> const operator+(Q<T> const& inValue1, Q<T> const& inValue2);
  template <typename T> Q<T> const operator-(Q<T> const& inValue1, Q<T> const& inValue2);
  template <typename T> Q<T> const operator*(Q<T> const& inValue1, Q<T> const& inValue2);
  template <typename T> Q<T> const operator/(Q<T> const& inValue1, Q<T> const& inValue2);

  template <typename T> bool operator==(Q<T> const& inValue1, Q<T> const& inValue2);
  template <typename T> bool operator!=(Q<T> const& inValue1, Q<T> const& inValue2);
  template <typename T> bool operator<=(Q<T> const& inValue1, Q<T> const& inValue2);
  template <typename T> bool operator>=(Q<T> const& inValue1, Q<T> const& inValue2);
  template <typename T> bool operator<(Q<T> const& inValue1, Q<T> const& inValue2);
  template <typename T> bool operator>(Q<T> const& inValue1, Q<T> const& inValue2);

  template <typename T> T const abs(T const& inValue);
  template <typename T> T const gcd(T const& inValue1, T const& inValue2);
  template <typename T> T const lcm(T const& inValue1, T const& inValue2);

  template <typename T = int>
  class Q {
    public:
      Q();
      Q(T const& inValue);
      Q(T const& inValue1, T const& inValue2);
      Q(Q<T> const& inValue);
//assignment operator
      Q<T>& operator=(Q<T> const& inValue);
//getter
      T& getNumerator(void);
      T& getDenominator(void);
//setter
      void setNumerator(T const& inValue);
      void setDenominator(T const& inValue);

      friend std::ostream& operator<< <>(std::ostream& ostr, Q<T> const& inValue);

      friend Q<T> const operator+ <>(Q<T> const& inValue1, Q<T> const& inValue2);
      friend Q<T> const operator- <>(Q<T> const& inValue1, Q<T> const& inValue2);
      friend Q<T> const operator* <>(Q<T> const& inValue1, Q<T> const& inValue2);
      friend Q<T> const operator/ <>(Q<T> const& inValue1, Q<T> const& inValue2);

      friend bool operator== <>(Q<T> const& inValue1, Q<T> const& inValue2);
      friend bool operator!= <>(Q<T> const& inValue1, Q<T> const& inValue2);
      friend bool operator<= <>(Q<T> const& inValue1, Q<T> const& inValue2);
      friend bool operator>= <>(Q<T> const& inValue1, Q<T> const& inValue2);
      friend bool operator< <>(Q<T> const& inValue1, Q<T> const& inValue2);
      friend bool operator> <>(Q<T> const& inValue1, Q<T> const& inValue2);

      friend T const abs<>(T const& inValue);
      friend T const gcd<>(T const& inValue1, T const& inValue2);
      friend T const lcm<>(T const& inValue1, T const& inValue2);

      bool isInteger();
    private:
      void setLowestTerm();
      void checkDenominator();
      T nValue;
      T dValue;
  };

  template <typename T>
  Q<T>::Q() {
    nValue = 0;
    dValue = 1;
  }

  template <typename T>
  Q<T>::Q(T const& inValue) {
    nValue = inValue;
    dValue = 1;
  }

  template <typename T>
  Q<T>::Q(T const& inValue1, T const& inValue2) {
    nValue = inValue1;
    dValue = inValue2;
    checkDenominator();
    setLowestTerm();
  }

  template <typename T>
  Q<T>::Q(Q<T> const& inValue) {
    nValue = inValue.nValue;
    dValue = inValue.dValue;
    checkDenominator();
    setLowestTerm();
  }

//assignment operator implementation
  template <typename T>
  Q<T>& Q<T>::operator=(Q<T> const& inValue) {
    nValue = inValue.nValue;
    dValue = inValue.dValue;
    checkDenominator();
    setLowestTerm();
  }

//getter
  template <typename T>
  T& Q<T>::getNumerator(void) {
    return nValue;
  }

  template <typename T>
  T& Q<T>::getDenominator(void) {
    return dValue;
  }

  template <typename T>
  std::ostream& operator<<(std::ostream& ostr, Q<T> const& inValue) {
    Q<T> temp = inValue;
    if (temp.isInteger()) ostr << temp.nValue;
    else ostr << temp.nValue << "/" << temp.dValue;
    return ostr;
  }

//friend functions - arithmetic operators
  template <typename T>
  Q<T> const operator+(Q<T> const& inValue1, Q<T> const& inValue2) {
    Q<T> temp;
    int tempLCM;
    tempLCM = lcm(inValue1.dValue, inValue2.dValue);
    temp.nValue = ((tempLCM / inValue1.dValue) * inValue1.nValue) + ((tempLCM / inValue2.dValue) * inValue2.nValue);
    temp.dValue = (T)tempLCM;
    temp.setLowestTerm();
    return temp;
  }

  template <typename T>
  Q<T> const operator-(Q<T> const& inValue1, Q<T> const& inValue2) {
    Q<T> temp;
    int tempLCM;
    tempLCM = lcm(inValue1.dValue, inValue2.dValue);
    temp.nValue = ((tempLCM / inValue1.dValue) * inValue1.nValue) - ((tempLCM / inValue2.dValue) * inValue2.nValue);
    temp.dValue = (T)tempLCM;
    temp.setLowestTerm();
    return temp;
  }

  template <typename T>
  Q<T> const operator*(Q<T> const& inValue1, Q<T> const& inValue2) {
    Q<T> temp;
    temp.nValue = inValue1.nValue * inValue2.nValue;
    temp.dValue = inValue1.dValue * inValue2.dValue;
    temp.setLowestTerm();
    return temp;
  }

  template <typename T>
  Q<T> const operator/(Q<T> const& inValue1, Q<T> const& inValue2) {
    Q<T> temp;
    temp.nValue = inValue1.nValue * inValue2.dValue;
    temp.dValue = inValue1.dValue * inValue2.nValue;
    temp.setLowestTerm();
    return temp;
  }

//friend functions - relational operators
  template <typename T>
  bool operator==(Q<T> const& inValue1, Q<T> const& inValue2) {
    T inValue1_int;
    T inValue1_rem;
    T inValue2_int;
    T inValue2_rem;
    inValue1_int = inValue1.nValue / inValue1.dValue;
    inValue1_rem = inValue1.nValue % inValue1.dValue;
    inValue2_int = inValue2.nValue / inValue2.dValue;
    inValue2_rem = inValue2.nValue % inValue2.dValue;
    if (inValue1_int == inValue2_int && inValue1_rem == inValue2_rem) return true;
    else return false;
  }

  template <typename T>
  bool operator!=(Q<T> const& inValue1, Q<T> const& inValue2) {
    T inValue1_int;
    T inValue1_rem;
    T inValue2_int;
    T inValue2_rem;
    inValue1_int = inValue1.nValue / inValue1.dValue;
    inValue1_rem = inValue1.nValue % inValue1.dValue;
    inValue2_int = inValue2.nValue / inValue2.dValue;
    inValue2_rem = inValue2.nValue % inValue2.dValue;
    if (inValue1_int != inValue2_int || inValue1_rem != inValue2_rem) return true;
    else return false;
  }

  template <typename T>
  bool operator<=(Q<T> const& inValue1, Q<T> const& inValue2) {
    T inValue1_int;
    T inValue1_rem;
    T inValue2_int;
    T inValue2_rem;
    inValue1_int = inValue1.nValue / inValue1.dValue;
    inValue1_rem = inValue1.nValue % inValue1.dValue;
    inValue2_int = inValue2.nValue / inValue2.dValue;
    inValue2_rem = inValue2.nValue % inValue2.dValue;
    if (inValue1_int < inValue2_int) return true;
    else if (inValue1_int == inValue2_int && inValue1_rem <= inValue2_rem) return true;
    else return false;
  }

  template <typename T>
  bool operator>=(Q<T> const& inValue1, Q<T> const& inValue2) {
    T inValue1_int;
    T inValue1_rem;
    T inValue2_int;
    T inValue2_rem;
    inValue1_int = inValue1.nValue / inValue1.dValue;
    inValue1_rem = inValue1.nValue % inValue1.dValue;
    inValue2_int = inValue2.nValue / inValue2.dValue;
    inValue2_rem = inValue2.nValue % inValue2.dValue;
    if (inValue1_int > inValue2_int) return true;
    else if (inValue1_int == inValue2_int && inValue1_rem >= inValue2_rem) return true;
    else return false;
  }

  template <typename T>
  bool operator<(Q<T> const& inValue1, Q<T> const& inValue2) {
    T inValue1_int;
    T inValue1_rem;
    T inValue2_int;
    T inValue2_rem;
    inValue1_int = inValue1.nValue / inValue1.dValue;
    inValue1_rem = inValue1.nValue % inValue1.dValue;
    inValue2_int = inValue2.nValue / inValue2.dValue;
    inValue2_rem = inValue2.nValue % inValue2.dValue;
    if (inValue1_int < inValue2_int) return true;
    else if (inValue1_int == inValue2_int && inValue1_rem < inValue2_rem) return true;
    else return false;
  }

  template <typename T>
  bool operator>(Q<T> const& inValue1, Q<T> const& inValue2) {
    T inValue1_int;
    T inValue1_rem;
    T inValue2_int;
    T inValue2_rem;
    inValue1_int = inValue1.nValue / inValue1.dValue;
    inValue1_rem = inValue1.nValue % inValue1.dValue;
    inValue2_int = inValue2.nValue / inValue2.dValue;
    inValue2_rem = inValue2.nValue % inValue2.dValue;
    if (inValue1_int > inValue2_int) return true;
    else if (inValue1_int == inValue2_int && inValue1_rem > inValue2_rem) return true;
    else return false;
  }

//friend functions - misc
  template <typename T>
  T const abs(T const& inValue) {
    T temp = inValue ;
    if (temp < 0 ) return (temp*(-1));
    return temp;
  }

  template <typename T>
  T const gcd(T const& inValue1, T const& inValue2) {
    T temp;
    T iv1Temp = (abs(inValue1));
    T iv2Temp = (abs(inValue2));
    while (iv2Temp != 0) {
      temp = iv2Temp;
      iv2Temp = iv1Temp % iv2Temp;
      iv1Temp = temp;
    }
    return iv1Temp;
  }

  template <typename T>
  T const lcm(T const& inValue1, T const& inValue2) {
    T temp;
    temp = abs(inValue1 * inValue2) / gcd(inValue1, inValue2);
    return temp;
  }

//member functions - is?
  template <typename T>
  bool Q<T>::isInteger() {
    if (nValue % dValue == 0) return true;
    return false;
  }

//private functions
  template <typename T>
  void Q<T>::checkDenominator() {
    if (dValue < 0) {
      nValue = nValue * -1;
      dValue = dValue * -1;
    }
    else if (dValue == 0) {
      std::cout << nValue << "/" << dValue << " is undefined.\n";
    }
  }

  template <typename T>
  void Q<T>::setLowestTerm() {
    T gcdTemp;
    gcdTemp = gcd(nValue,dValue);
    nValue = nValue / gcdTemp;
    dValue = dValue / gcdTemp;
  }

}

#endif
